package com.mrx.xfriendship.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mrx.commons.annotations.IgnoreLog;
import com.mrx.commons.annotations.XLog;
import com.mrx.commons.exception.BusinessException;
import com.mrx.commons.util.asserts.Asserts;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.mrx.commons.config.RedisConstant.*;

/**
 * @author : 肖康宁
 * @since : 2022-12-09 10:00
 **/
@XLog
@Service
@DubboService
public class RedisServiceImpl implements IRedisService {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);

    @Override
    public void setUserToken(String name, String token) {
        setUserToken(name, token, DEFAULT_TOKEN_TTL);
    }

    @Override
    public void setUserToken(String name, String token, long timeout) {
        redisTemplate.opsForValue().set(getUserKey(name), token, timeout, TimeUnit.MINUTES);
    }

    @NonNull
    @Override
    public String getUserToken(String name) throws BusinessException {
        String token = Asserts.TOKEN_EXPIRED.checkNotNull(redisTemplate.opsForValue().get(getUserKey(name)));
        redisTemplate.expire(getUserKey(name), DEFAULT_TOKEN_TTL, TimeUnit.MINUTES);
        return token;
    }

    @Override
    public void setCaptcha(String key, String value) {
        redisTemplate.opsForValue().set(getCaptchaKey(key), value, 1, TimeUnit.MINUTES);
    }

    @Nullable
    @Override
    public String getCaptcha(String key) {
        return redisTemplate.opsForValue().getAndDelete(getCaptchaKey(key));
    }

    @Override
    public void removeUserToken(String userName) {
        redisTemplate.delete(getUserKey(userName));
    }

    @Override
    public void plusSitePV(String location) {
        plusPV(getSitePVKey(), location);
    }

    @Override
    public JSONObject getSitePvList(boolean delete) {
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        return JSON.parseObject(delete ? opsForValue.getAndDelete(getSitePVKey()) : opsForValue.get(getSitePVKey()));
    }

    @Override
    public void plusPostPV(Integer postId) {
        plusPV(getPostPVKey(), postId + "");
    }

    @Override
    public Integer getPostPV(Integer postId) {
        JSONObject json = getPostPVList(false);
        if (json == null) return 0;
        return json.getInteger(String.valueOf(postId));
    }

    @Override
    public JSONObject getPostPVList(boolean isSync) {
        String res;
        if (isSync) {
            res = redisTemplate.opsForValue().getAndDelete(getPostPVKey());
        } else {
            res = redisTemplate.opsForValue().get(getPostPVKey());
        }
        return JSONObject.parseObject(res);
    }

    @Override
    @IgnoreLog
    public void plusOnlineUser(Integer currentUserId) {
        redisTemplate.opsForValue().set(getOnlineUserKey(currentUserId + ""), "1", Duration.ofMinutes(1));
    }

    @Override
    public void minusOnlineUser(Integer currentUserId) {
        redisTemplate.delete(getOnlineUserKey(currentUserId + ""));
    }

    @NonNull
    @Override
    public Integer getOnlineUser() {
        return Optional.ofNullable(redisTemplate.keys(getOnlineUserKey("*"))).map(Set::size).orElse(0);
    }

    @Override
    public void setBindMail(String userName, String value) {
        redisTemplate.opsForValue().set(getBindMailUserKey(userName), value, Duration.ofMinutes(10));
    }

    @Override
    public Optional<String> getBindMail(String userName) {
        return Optional.ofNullable(redisTemplate.opsForValue().getAndDelete(getBindMailUserKey(userName)));
    }

    @Override
    public Boolean setRateLimiter(String key, Integer timeout) {
        return redisTemplate.opsForValue().setIfAbsent(getRateLimiterKey(key), "1", Duration.ofSeconds(timeout));
    }

    @Override
    public Optional<Long> getRateLimiter(String key) {
        return Optional.ofNullable(redisTemplate.getExpire(getRateLimiterKey(key)));
    }

    @NonNull
    private String getRateLimiterKey(String key) {
        return RATE_LIMITER_PREFIX + key;
    }

    @NonNull
    private String getBindMailUserKey(String userName) {
        return BIND_MAIL_PREFIX + userName;
    }

    @NonNull
    private String getOnlineUserKey(String userId) {
        return ONLINE_USER_PREFIX + userId;
    }

    @NonNull
    private String getPostPVKey() {
        return PV_PREFIX + "post";
    }

    private void plusPV(String redisKey, String jsonKey) {
        JSONObject json = JSON.parseObject(redisTemplate.opsForValue().get(redisKey));
        if (json == null) {
            // 设置 sitePVKey 初始值
            redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(Map.of(jsonKey, 1)));
        } else {
            // 设置 location 初始值
            json.putIfAbsent(jsonKey, 1);
            json.computeIfPresent(jsonKey, (k, old) -> Integer.parseInt(old + "") + 1);
            redisTemplate.opsForValue().set(redisKey, json.toJSONString());
        }
        logger.debug("json: {}", json);
    }

    @NonNull
    private String getSitePVKey() {
        return PV_PREFIX + "site";
    }

    @NonNull
    private String getUserKey(@NonNull String userName) {
        return USER_PREFIX + userName;
    }

    @NonNull
    private String getCaptchaKey(String key) {
        return CAPTCHA_PREFIX + key;
    }

}
